/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Copyright 2002-2003 by Mark Wolgemuth, All rights reserved.             */
/* Copyright 2002-2003 by Employease, Inc.  All rights reserved.           */
/*                                                                         */
/* Redistribution and use in source and binary forms, with or without      */
/* modification, are permitted provided that the following conditions      */
/* are met:                                                                */
/*                                                                         */
/*    1. Redistributions of source code must retain the above copyright    */
/* notice, this list of conditions and the following disclaimer.           */
/*    2. Redistributions in binary form must reproduce the above copyright */
/* notice, this list of conditions and the following disclaimer in the     */
/* documentation and/or other materials provided with the distribution.    */
/*                                                                         */
/* THIS SOFTWARE IS PROVIDED BY THE MOD_ATHENA PROJECT ``AS IS'' AND ANY   */
/* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE       */
/* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR      */
/* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR        */
/* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,   */
/* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,     */
/* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR      */
/* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY     */
/* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT            */
/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   */
/* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.    */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


#include "athd.h"

static void _athd_connection_destroy(athd_connection **conn);
static apr_status_t _athd_connection_filter_setup(apr_pool_t *pool);
static apr_status_t _athd_connection_filters(athd_connection *conn);
static apr_status_t _athd_connection_lock(athd_connection *conn);
static apr_status_t _athd_connection_unlock(athd_connection *conn);

apr_status_t
athd_connection_shared_setup(apr_pool_t *pglobal)
{
    int rv = APR_SUCCESS;
    _athd_connection_filter_setup(pglobal);
    return rv;
}


apr_status_t
athd_connection_create(athd_connection **conn, const apr_uri_t *uri,
		       apr_pool_t *pool)
{
    athd_connection *conn_this;
    apr_pool_t *opool;
    int rv;
    apr_pool_create(&opool, pool);
    conn_this = apr_pcalloc(opool, sizeof(athd_connection));
    conn_this->pool = opool;
    conn_this->mutex = apr_pcalloc(conn_this->pool, sizeof(pthread_mutex_t));
    apr_pool_cleanup_register(conn_this->pool, conn_this->mutex,
			      pthread_mutex_cleanup, apr_pool_cleanup_null);
    pthread_mutex_init(conn_this->mutex, NULL);
    conn_this->ref_count = 0;
    conn_this->retire = 0;
    if (strcasecmp(uri->scheme, "https") == 0)
	conn_this->use_ssl = 1;
    else
	conn_this->use_ssl = 0;
    conn_this->serf_conn = serf_create_connection(conn_this->pool);
    rv = apr_sockaddr_info_get(&conn_this->serf_conn->address, 
			       uri->hostname,
			       APR_INET, uri->port, 0,
			       conn_this->pool);
    RV_OK = apr_socket_create(&conn_this->serf_conn->socket,
			      APR_INET, SOCK_STREAM,
			      conn_this->pool);
    RV_OK = apr_socket_opt_set(conn_this->serf_conn->socket,
			       APR_SO_KEEPALIVE, 1);
    RV_OK = apr_socket_opt_set(conn_this->serf_conn->socket,
			       APR_SO_LINGER, 1);
    RV_OK = _athd_connection_filters(conn_this);
    if (rv)
	_athd_connection_destroy(&conn_this);
    (*conn) = conn_this;
    return rv;
}

void
athd_connection_acquire(athd_connection *conn)
{
    _athd_connection_lock(conn);
    conn->ref_count++;
    _athd_connection_unlock(conn);
}

void
athd_connection_release(athd_connection **conn)
{
    ASSERT(*conn);
    _athd_connection_lock(*conn);
    (*conn)->ref_count--;
    _athd_connection_unlock(*conn);
    /* ref count can be -1 if release is called before it was ever
       REALLY acquired. this is happens when a connection is bad from the 
       start, since acquire is only called in serf async call back.
    */
    athd_log(ATHD_DEVEL, 
	     "Connection: Release: retired: [%d] pending reqs: [%d]", 
	     athd_connection_is_retired((*conn)), (*conn)->ref_count);
    if (((*conn)->retire) && ((*conn)->ref_count < 1))
	_athd_connection_destroy(conn);
}

void
athd_connection_retire(athd_connection *conn)
{
    ASSERT(conn);
    _athd_connection_lock(conn);
    conn->retire = 1;
    _athd_connection_unlock(conn);
    athd_log(ATHD_DEVEL, "Connection: Retire: [%d]", conn);
}

int
athd_connection_is_retired(athd_connection *conn)
{
    int status;
    _athd_connection_lock(conn);
    status = conn->retire;
    _athd_connection_unlock(conn);
    return status;
}

apr_status_t
athd_connection_do_serf(athd_request *req)
{
    apr_status_t rv;
    char *errmsg = NULL;
    ASSERT(req);
    ASSERT(req->conn);
    ASSERT(req->conn->serf_conn);
    
    athd_req_serf_req_build(req);
    rv = serf_open_connection(req->conn->serf_conn);

    if ((!rv) || (rv == EISCONN)) /* EISCONN returned for keep-alive */
      {
	  rv = serf_write_request(req->serf_request, req->conn->serf_conn);
	  if ((!rv) || (rv == EISCONN))
	    {
		rv = serf_read_response(&req->serf_response,
					req->conn->serf_conn, req->pool);
		if (APR_STATUS_IS_EAGAIN(rv))
		  {
		      req->engine->slow_server_factor++;
		      athd_log(ATHD_WARN, "Connection: Busy, raising engine "
			       "[%s] slow factor to: [%d]", req->engine->name, 
			       req->engine->slow_server_factor);
		  }
		else if (rv)
		    errmsg = "Connection: Read: ";
	    }
	  else
	      errmsg = "Connection: Write: ";
      }
    else
	errmsg = "Connection: Open: ";
    
    if (errmsg)
	athd_log_err(ATHD_WARN, rv, errmsg);
    
    return rv;
}
		    
void
athd_connection_reset(int sig)
{
    athd_log(ATHD_WARN, "Connection: Reset by peer");
}

static apr_status_t
_athd_connection_filter_setup(apr_pool_t *pool)
{
    serf_filter_t *filter;
    serf_register_filter("SOCKET_WRITE", serf_socket_write, pool);
    serf_register_filter("SOCKET_READ", serf_socket_read, pool);
#if SERF_HAS_OPENSSL
    serf_register_filter("SSL_WRITE", serf_ssl_write, pool);
    serf_register_filter("SSL_READ", serf_ssl_read, pool);
#endif
    serf_register_filter("USER_AGENT", 
			 athd_filter_header_agent, pool);
    serf_register_filter("HOST_HEADER", 
			 athd_filter_header_host, pool);
    serf_register_filter("HTTP_HEADERS_OUT", 
			 athd_filter_http_headers, pool);
    serf_register_filter("HTTP_STATUS_IN", 
			 serf_http_status_read, pool);
    serf_register_filter("HTTP_HEADERS_IN", 
			 serf_http_header_read, pool);
    serf_register_filter("HTTP_DECHUNK", serf_http_dechunk, pool);
    serf_register_filter("HTTP_RESP_LEN", athd_filter_resp_len, pool);

    return APR_SUCCESS;
}

static apr_status_t
_athd_connection_lock(athd_connection *conn)
{
    ASSERT(conn->mutex);
    return pthread_mutex_lock(conn->mutex);
}

static apr_status_t
_athd_connection_unlock(athd_connection *conn)
{
    ASSERT(conn->mutex);
    return pthread_mutex_unlock(conn->mutex);
}

static apr_status_t
_athd_connection_filters(athd_connection *conn)
{
    int rv;
    serf_filter_t *filter;

    /* since we send all urls through one connection, we */
    /* can do this work now */

    if (conn->use_ssl)
      {
	  filter = serf_add_filter(conn->serf_conn->request_filters,
				   "SSL_WRITE", conn->pool);
	  filter->ctx = conn->serf_conn;
	  filter = serf_add_filter(conn->serf_conn->response_filters,
				   "SSL_READ", conn->pool);
	  filter->ctx = conn->serf_conn;
      }
    else
      {
	  filter = serf_add_filter(conn->serf_conn->request_filters,
				   "SOCKET_WRITE", conn->pool);
	  filter->ctx = conn->serf_conn;
	  filter = serf_add_filter(conn->serf_conn->response_filters,
				   "SOCKET_READ", conn->pool);
	  filter->ctx = conn->serf_conn;
      }
    rv = APR_SUCCESS;
    return rv;
}
 
static void
_athd_connection_destroy(athd_connection **conn)
{
    apr_pool_t *pool;
    ASSERT(*conn);
    if ((*conn)->serf_conn->socket)
	serf_close_connection((*conn)->serf_conn);
    pool = (*conn)->pool;
    apr_pool_destroy(pool);
    (*conn) = NULL;
}
